
namespace Nutils 
{
    namespace P4
    {
        public class Exception : Nutils.Exception
        {
            public Exception( string s)
                :base(s)
            {
            }
        }

        public class Dict : System.Collections.Generic.Dictionary<string, string>{}
        public class DictList : System.Collections.Generic.List<Dict>{}
        
        public class CLList : System.Collections.Generic.List<Changelist>{ }
        public class CLFileList : System.Collections.Generic.List<CLFile> { }
        public class DepoFileList : System.Collections.Generic.List<CLFile> { }
        public class JobList : System.Collections.Generic.List<Job>{ }

        public class Depo
        {
            public string User;
            public string ClientSpec;
            public string Host { get { return _p4.Host; } }

            protected class P4ComWrapper
            {
                public string Host { get { return _p4.Port; } }

                public P4ComWrapper(string Host, string Password)
                {
                    _p4 = new P4COM.p4();
                    _p4.Password = Password;
                    _p4.Port = Host;

                    _p4.ParseForms();
                    _p4.Connect();
                }
                
                public delegate void OnRunError(string sCmd, string sError);


                public virtual DictList Run(string sCmd)
                {
                    return Run(sCmd, _LogError);
                }

                public virtual DictList Run(string sCmd, OnRunError callback)
                {
                    System.Array lUnformated = _Run(sCmd, callback);
                    DictList lDicts = new DictList();
                    Dict mCurr = new Dict();

                    foreach (string s in lUnformated)
                    {
                        Nutils.Console.Spam("P4 run result: \"" + s + "\"");
                        int i = s.IndexOf(' ');
                        string sKey = s.Substring(0, i);
                        string sData = s.Substring(i + 1);

                        if (mCurr.ContainsKey(sKey))
                        {
                            lDicts.Add(mCurr);
                            mCurr = new Dict();
                        }

                        mCurr.Add(sKey, sData);
                    }

                    if (mCurr.Count > 0)
                    {
                        lDicts.Add(mCurr);
                    }

                    return lDicts;
                }

                public string RunUnformated(string cmd)
                {
                    System.Array l = _Run(cmd, _LogError);
                    string ret = "";
                    foreach( string s in l)
                    {
                        ret += s;
                    }
                    return ret;
                }

                public System.Array _Run(string cmd, OnRunError callback)
                {
                    Nutils.Console.Spam("Running P4 command: \"" + cmd + "\"");
                    try
                    {
                        return _p4.run(cmd);
                    }
                    catch (System.Exception ex)
                    {
                        callback(cmd, ex.Message);
                    }

                    return new System.Collections.Generic.List<string>().ToArray();
                }

                private void _LogError(string sCmd, string sError)
                {
                    Nutils.Console.Error(sError);
                }

                private P4COM.p4 _p4;
            }

            virtual protected P4ComWrapper P4COM_Allocate(string Host, string Password)
            {
                return new P4ComWrapper(Host, Password);
            }

            public DictList __Run(string cmd)
            {
                return _p4.Run(cmd);
            }

            public Depo(string Host, string User, string ClientSpec, string Password)
            {
                this.ClientSpec = ClientSpec;
                this.User = User;
                _p4 = P4COM_Allocate(Host, Password);
            }

            public CLList ChangeLists
            {
                get
                {
                    CLList changes = new CLList();
                    DictList runResult = _p4.Run("changes -u " + User + " -c " + ClientSpec + " -s pending");
                    foreach (Dict d in runResult)
                    {
                        Changelist change = new Changelist(this, d["change"]);
                        changes.Add(change);
                    }

                    changes.Add(new DefaultChangelist(this));
                    return changes;
                }
            }

            public JobList Jobs
            {
                get
                {
                    JobList jobs = new JobList();
                    DictList lRunResult = _p4.Run("jobs");
                    foreach (Dict m in lRunResult)
                    {
                        Job job = new Job(m);
                        jobs.Add(job);
                    }

                    return jobs;
                }
            }

            public bool IsFileCheckedOut(string sFile)
            {
                DictList mRet = _p4.Run("opened \"" + sFile + "\"");
                return mRet.Count > 0;
            }

            public DictList SyncToHead(bool bClobberWritable)
            {
                if( bClobberWritable )
                    return _p4.Run("sync", _ClobberWritable);

                return _p4.Run("sync");
            }

            private void _ClobberWritable(string sCmd, string sError)
            {
                string sCannotClobber = "Can't clobber writable file";
                
                if (!sError.Contains(sCannotClobber))
                {
                    return;

                }

                string[] sCannotClobberSep = new string[] { sCannotClobber };
                string[] sHashSep = new string[] { sCannotClobber };

                string sTrimmedError = sError.Trim();
                string[] sFilesToClobber = sTrimmedError.Split(sCannotClobberSep, System.StringSplitOptions.None);

                foreach (string sFileToClobber in sFilesToClobber)
                {
                    string sTrimmedFileToClobber = sFileToClobber.Trim();
                    if (sTrimmedFileToClobber.Length < 1)
                    {
                        continue;
                    }

                    if (sTrimmedFileToClobber.Contains("#"))
                    {
                        string[] sSplitted = sTrimmedError.Split(sHashSep, System.StringSplitOptions.None);
                        if (sSplitted.Length > 1)
                        {
                            SyncFile_Forced(sTrimmedFileToClobber, sSplitted[1]);
                            continue;
                        }
                    }

                    SyncFile_Forced(sTrimmedFileToClobber);
                }
            }


            private void _IgnoreFileUpToDateMsg(string sCmd, string sError)
            {
                if (sCmd.ToLower().Contains("sync") && sError.Contains("file(s) up-to-date"))
                {
                    return;
                }

                Nutils.Console.Error(sError);
            }

            public DictList SyncFileToRev(string sFile, string sRev)
            {
                return _p4.Run("sync \"" + sFile + "#" + sRev + "\"", _IgnoreFileUpToDateMsg);
            }

            public DictList SyncFile_Forced(string sFile)
            {
                return _p4.Run("sync -f \"" + sFile + "\"");
            }

            public DictList SyncFile_Forced(string sFile, string sRev)
            {
                return _p4.Run("sync -f \"" + sFile + "#" + sRev + "\"");
            }

            public string GetFileLocalPath(string sFile)
            {
                string sWhereCommand = "where \"" + sFile + "\"";
                DictList lWhere = _p4.Run(sWhereCommand);
                if (lWhere.Count > 0)
                {
                    Dict mWhere = lWhere[0];
                    if (mWhere.ContainsKey("path"))
                    {
                        return mWhere["path"];
                    }
                    
                    // Odds are good the code wasn't returned formatted.
                    Nutils.Console.Error("Failed to get a formatted response back. Would love to know why this happens on some depos and not others, and then only with the where command...");

                    string sUnformated = _p4.RunUnformated(sWhereCommand);

                    int iIndex = sFile.LastIndexOf('/');
                    string[] sSep = new string[] { sFile.Substring(iIndex + 1) };
                    string[] lRoots = sUnformated.Split(sSep, System.StringSplitOptions.None);


                    return lRoots[2] + sSep; // Local file path
                    //return lRoots[1] + sSep; // client spec's path
                    //return lRoots[0] + sSep; // depo's path
                }

                return "";
            }

            public DictList OpenedFilesInChangelist(string changeID)
            {
                return _p4.Run("opened -c " + changeID);
            }

            public Dict DecribeChangelist(string changeID)
            {
                return _p4.Run("describe " + changeID)[0];
            }

            public void Revert(string file)
            {
                DictList result = _p4.Run("revert \"" + file + "\"");
                // TODO: Parse result for errors and whatnot.
            }

            public string GetCurrentFileRevision(string file)
            {
                Dict dict = FileStat(file);
                if (dict.ContainsKey("haveRev"))
                {
                    return dict["haveRev"];
                }
                return "0";
            }

            public Dict FileStat(string file)
            {
                DictList mResult = _p4.Run("fstat \"" + file + "\"");
                if (mResult.Count > 0)
                {
                    return mResult[0];
                }

                Dict noInfo = new Dict();
                noInfo["action"] = "???";
                noInfo["type"] = "???";
                noInfo["change"] = "???";
                noInfo["clientFile"] = "???";
                noInfo["headType"] = "???";
                noInfo["headRev"] = "???";
                noInfo["headAction"] = "???";
                noInfo["haveRev"] = "0";

                return noInfo;
            }

            public bool AutoResolve(string file)
            {
                DictList ret = _p4.Run("resolve -as \"" + file + "\"");
                if (ret.Count > 0 && 
                    ret[0].ContainsKey("Diff") &&
                    ret[0]["Diff"].Contains("0 conflicting") )
                {
                    Nutils.Console.Spam("Successfully auto-resolved \"" + file + "\"");
                    return true;
                }

                Nutils.Console.Notify("Failed to auto-resolve \"" + file + "\"");
                return false;
            }

            public DictList Checkout(string file)
            {
                return _p4.Run("edit \"" + file + "\"");
            }

            public DictList Add(string file)
            {
                return _p4.Run("add \"" + file + "\"");
            }

            public DictList Delete(string file)
            {
                return _p4.Run("delete \"" + file + "\"");
            }

            // --- Privates ---
            private P4ComWrapper _p4;
        }
        
        public class MultiDepo
        {
            public MultiDepo()
            {
                _depos = new System.Collections.Generic.Dictionary<string, Depo>();
            }

            public void Connect(string sAdder, string sUser, string sClientSpec, string sPass)
            {
                _depos.Add(sAdder.ToLower(), new Nutils.P4.Depo(sAdder, sUser, sClientSpec, sPass));
            }

            public bool Connected
            {
                get
                {
                    return _depos.Count > 0;
                }
            }

            public P4.Depo FindDepo(string sPort)
            {
                if (!_depos.ContainsKey(sPort.ToLower()))
                {
                    throw new Nutils.P4.Exception("Not connected to depo: " + sPort);
                }

                return _depos[sPort.ToLower()];
            }

            public JobList Jobs
            {
                get
                {
                    JobList jobs = new JobList();
                    foreach (Depo depo in _depos.Values)
                    {
                        jobs.AddRange(depo.Jobs);
                    }
                    return jobs;
                }
            }
            public CLList ChangeLists
            {
                get
                {
                    CLList changelists = new CLList();
                    foreach (Depo depo in _depos.Values)
                    {
                        changelists.AddRange(depo.ChangeLists);
                    }
                    return changelists;
                }
            }
            private System.Collections.Generic.Dictionary<string,Depo> _depos;
            public System.Collections.Generic.Dictionary<string,Depo>.ValueCollection Depos
            {
                get
                {
                    return _depos.Values;
                }
            }
        }

        public class Job
        {
            public readonly string Name;
            public string Status { get { return _info["Status"]; } }
            public string User { get { return _info["User"]; } }
            public string Date { get { return _info["Date"]; } }
            public string Description { get { return _info["Description"]; } }

            public Job(Dict info)
            {
                Name = info["Job"];
                _info = info;
            }

            // --- Privates ---
            private Dict _info;
        }

        public class Changelist
        {
            public readonly string ChangeID;
            public float Time;
            public string User { get { return _info["user"]; } }
            public string Client { get { return _info["client"]; } }
            public string Status { get { return _info["status"]; } }
            public string Description { get { return _info["desc"]; } }
            public CLFileList Files;

            public Changelist(Depo depo, string ChangeID)
            {
                this._depo = depo;
                this.ChangeID = ChangeID;
                Rebuild();
            }

            public void Rebuild()
            {
                // build information about this change list.
                _info = _Rebuild_Info(_depo);
                Files = _Rebuild_FileList(_depo);
            }

            // --- Protecteds ---

            virtual protected CLFileList _Rebuild_FileList(Depo cDepo)
            {
                // build a list of files in this change list.
                DictList OpenedFilesInChangelist = cDepo.OpenedFilesInChangelist(ChangeID);
                CLFileList lCLFiles = new CLFileList();

                foreach (Dict m in OpenedFilesInChangelist)
                {
                    if (m.Count <= 0)
                    {
                        continue;
                    }

                    CLFile cFile = new CLFile(cDepo, m["depotFile"]);
                    lCLFiles.Add(cFile);
                }

                return lCLFiles;
            }

            virtual protected Dict _Rebuild_Info(Depo cDepo)
            {
                return cDepo.DecribeChangelist(ChangeID);
            }

            // --- Privates ---
            private Dict _info;
            private Depo _depo;
        }

        public class DefaultChangelist : Changelist
        {
            public DefaultChangelist(Depo cDepo)
                : base(cDepo, "default")
            {

            }

            override protected Dict _Rebuild_Info(Depo depo)
            {
                Dict d = new Dict();
                d.Add("user", depo.User);
                d.Add("client", depo.ClientSpec);
                d.Add("status", "pending");
                d.Add("desc", "");
                return d;
            }
        }

        public class CLFile
        {
            public readonly string DepoFile;
            public string Action { get { return _info["action"]; } }
            public string Type { get { return _info["type"]; } }
            public string ChangeID { get { return _info["change"]; } }
            public string DepoHost { get { return _depo.Host; } }
            public string ClientFile { get { return _info["clientFile"]; } }
            public string HeadType { get { return _Get(_info, "headType"); } }
            public string ReadRev { get { return _Get(_info, "headRev"); } }
            public string HeadAction { get { return _Get(_info, "headAction"); } }
            public string HaveRev { get { return _Get(_info, "haveRev", "0"); } }
            
            public CLFile(Depo depo, string depoFile)
            {
                this._depo = depo;
                this.DepoFile = depoFile;
                Rebuild();
            }

            public void Revert()
            {
                _depo.Revert(DepoFile);
            }

            public void Rebuild()
            {
                _info = _depo.FileStat(DepoFile);
            }

            // --- Privates ---
            private Dict _info;
            private Depo _depo;

            static private string _Get(Dict info, string key)
            {
                if (info["action"].Equals("add"))
                {
                    return "";
                }
                return info[key];
            }

            static private string _Get(Dict info, string key, string fallback)
            {
                if (info["action"].Equals("add"))
                {
                    return fallback;
                }
                return info[key];
            } 

        }        
    } // end P4
} // end Nutils
